#include "GraphBuilder.h"
#include <sstream>
#include <fstream>
#include <algorithm>
#include <math.h>

const double GraphBuilder::BETA = 2.0;
Node* GraphBuilder::FindNode(std::list<Node*> &nodes, std::string &sId, NodeType tp)
{
	std::list<Node*>::iterator it;
	Node *node;
	it = std::find_if(nodes.begin(), nodes.end(), NodeFinder(sId, tp));
	if (it != nodes.end())
	{
		node = (Node*)(*it);
	}
	else
	{
		node = new Node(sId, tp);
		nodes.push_back(node);
	}
	return node;
}

void GraphBuilder::UpdateGraph(Graph &grp, std::map<Node*, double> &mapWeights,
							   Node* nodeStart, Node *nodeEnd, double dWeight)
{
	ListEdge* lsEdges;
	if (grp.find(nodeStart) == grp.end())
	{
		lsEdges = new ListEdge();
		lsEdges->push_back(new Edge(nodeEnd, dWeight));
		grp[nodeStart] = lsEdges;
		mapWeights[nodeStart] = dWeight;
	}
	else
	{
		lsEdges = grp[nodeStart];
		ListEdge::iterator it = std::find_if(lsEdges->begin(),
			lsEdges->end(), EdgeFinder(nodeEnd));
		if (it == lsEdges->end())
		{
			// Restrict the maximum number of nodes: 100
			//if (lsEdges->size() < 100)
			//{
			lsEdges->push_back(new Edge(nodeEnd, dWeight));
			//}
		}
		else
		{
			(*it)->weight += dWeight;
		}
		mapWeights[nodeStart] += dWeight;
	}
}

AdsorptionData* GraphBuilder::ReadData(std::string &sInputFile)
{
	std::ifstream fData;
	std::string sLine, sUserID, sSongID;
	double dWeight;
	Node *user, *item;
	std::map<Node*, double> mpTotalWeight;

	fData.open(sInputFile.c_str());
	if (!fData.is_open())
		return NULL;

	AdsorptionData* retData = new AdsorptionData();
	while (!fData.eof())
	{
		std::getline(fData, sLine);
		std::stringstream streamLine(sLine);
		streamLine >> sUserID >> sSongID >> dWeight;

		user = FindNode(retData->nodes, sUserID, NodeTypeUser);
		item = FindNode(retData->nodes, sSongID, NodeTypeItem);

		UpdateGraph(retData->graphUsers, mpTotalWeight, user, item, dWeight);
		UpdateGraph(retData->graphItems, mpTotalWeight, item, user, dWeight);
	}
	fData.close();

	Graph::iterator itMap;
	for (itMap = retData->graphUsers.begin(); itMap != retData->graphUsers.end(); itMap++)
	{
		ListLabel *labels = new ListLabel();
		double H_v(0.0), c_v, d_v, z_v;
		for (ListEdge::iterator itEdge = itMap->second->begin();
			itEdge != itMap->second->end(); itEdge++)
		{
			// update the label distribution
			double dProb = ((*itEdge)->weight) / mpTotalWeight[itMap->first];
			//labels->push_back(new Label((*itEdge)->data->Id, dProb));

			// calculates the entropy of vertex v
			H_v -= dProb * log(dProb);
		}
		retData->labelDistributions[itMap->first] = labels;

		// calculates probabilities for each vertex
		c_v = log(BETA) / log(BETA + exp(H_v));
		d_v = 0;
		z_v = std::max<double>(c_v + d_v, 1);
		c_v /= z_v;
		d_v /= z_v;

		// cont, inj and abnd
		ListNumber* probs = new ListNumber();
		probs->push_back(c_v);
		probs->push_back(d_v);
		probs->push_back(1 - c_v - d_v);
		retData->probabilities[itMap->first] = probs;
	}

	for (itMap = retData->graphItems.begin(); itMap != retData->graphItems.end(); itMap++)
	{
		ListLabel *labels = new ListLabel();
		double H_v(0.0), c_v, d_v, z_v;
		for (ListEdge::iterator itEdge = itMap->second->begin();
			itEdge != itMap->second->end(); itEdge++)
		{
			// update the label distribution
			double dProb = ((*itEdge)->weight) / mpTotalWeight[itMap->first];

			// calculates the entropy of vertex v
			H_v -= dProb * log(dProb);
		}

		labels->push_back(new Label((itMap)->first->Id, 1.0));
		retData->labelDistributions[itMap->first] = labels;

		// calculates probabilities for each vertex
		c_v = log(BETA) / log(BETA + exp(H_v));
		d_v = (1 - c_v)*sqrt(H_v);
		z_v = std::max<double>(c_v + d_v, 1);
		c_v /= z_v;
		d_v /= z_v;

		// cont, inj and abnd
		ListNumber* probs = new ListNumber();
		probs->push_back(c_v);
		probs->push_back(d_v);
		probs->push_back(1 - c_v - d_v);
		retData->probabilities[itMap->first] = probs;
	}

	return retData;
	
}